﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Measures;
using DiscoveryLogic.Analytics.DataObjects.Networks;
using DiscoveryLogic.Common.Utilities;

namespace DiscoveryLogic.Analytics.BusinessLogic.Measures
{
    public class VerticeMeasureManager
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="networkName"></param>
        /// <returns></returns>
        public static List<VerticeMeasure> GetVerticeMeasures(string networkName)
        {
            List<VerticeMeasure> measures=new List<VerticeMeasure>();
            string appFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolderPath = Path.Combine(appFolderPath, "Network Data");
            if (!Directory.Exists(appFolderPath))
                Directory.CreateDirectory(appFolderPath);
            string netMeasureFilePath = Path.Combine(appFolderPath, networkName + ".NetMeasure");
            bool readFromCache = false;
            if (File.Exists(netMeasureFilePath))
            {
                XmlDocument xDoc = new XmlDocument();
                try
                {
                    xDoc.Load(netMeasureFilePath);
                    XmlNode root = xDoc.DocumentElement;
                    int vCount = int.Parse(XmlDataUtil.GetAttributeValue(root, "VerticeCount", "0"));
                    int lCount = int.Parse(XmlDataUtil.GetAttributeValue(root, "LinkCount", "0"));
                    Network network = Network.Read(networkName);
                    if (network.Records.Count == vCount && network.Links.Count == lCount)
                        readFromCache = true;
                    XmlNodeList vMeasureNodes = root.SelectNodes("Vertices/Vertice");
                    if (vMeasureNodes != null && vMeasureNodes.Count > 0)
                    {
                        foreach (XmlNode measureNode in vMeasureNodes)
                        {
                            VerticeMeasure vMeasure = new VerticeMeasure();
                            vMeasure.ID = XmlDataUtil.GetAttributeValue(measureNode, "ID", "");
                            vMeasure.Name = XmlDataUtil.GetAttributeValue(measureNode, "Name", "");
                            vMeasure.EntityType =
                                (EntityType)
                                Enum.Parse(
                                    typeof(EntityType),
                                    XmlDataUtil.GetAttributeValue(measureNode, "EntityType", EntityType.Grants.ToString()));
                            vMeasure.Degree = int.Parse(XmlDataUtil.GetAttributeValue(measureNode, "Degree", "0"));
                            vMeasure.InDegree = int.Parse(XmlDataUtil.GetAttributeValue(measureNode, "InDegree", "0"));
                            vMeasure.OutDegree = int.Parse(XmlDataUtil.GetAttributeValue(measureNode, "OutDegree", ""));
                            vMeasure.BetweenCentrality =
                                double.Parse(XmlDataUtil.GetAttributeValue(measureNode, "BetweenCentrality", "0"));
                            vMeasure.CloseCentrality =
                                double.Parse(XmlDataUtil.GetAttributeValue(measureNode, "CloseCentrality", "0"));
                            vMeasure.Flow = int.Parse(XmlDataUtil.GetAttributeValue(measureNode, "Flow", "0"));
                            measures.Add(vMeasure);
                        }
                    }
                }
                catch
                {
                    readFromCache = false;
                }
            }
            
            if(!readFromCache)
            {
                Network network = Network.Read(networkName);
                
                Dictionary<Guid, Vertice> vertices = new Dictionary<Guid, Vertice>();
                foreach (EntityBase entity in network.Records)
                {
                    Guid guid = Guid.NewGuid();
                    Vertice v = new Vertice(guid, entity);
                    vertices.Add(guid, v);
                }
                foreach (Guid vID in vertices.Keys)
                {
                    VerticeMeasure vMeasure = new VerticeMeasure();
                    vMeasure.ID = vertices[vID].Tag.PKValue();
                    vMeasure.Name = vertices[vID].Tag.Name;
                    vMeasure.EntityType = vertices[vID].Tag.EntityType;
                    vMeasure.Degree = 1;
                    vMeasure.InDegree = 1;
                    vMeasure.OutDegree = 1;
                    vMeasure.BetweenCentrality = 0;
                    vMeasure.CloseCentrality = 0;
                    vMeasure.Flow = 0;
                    measures.Add(vMeasure);
                }
                
                BackgroundWorker bgWorker=new BackgroundWorker();
                bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
                bgWorker.RunWorkerAsync(network);
            }

            return measures;
        }

        static void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            Network network = (Network) e.Argument;
            List<VerticeMeasure> measures = new List<VerticeMeasure>();
            string appFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolderPath = Path.Combine(appFolderPath, "Network Data");
            if (!Directory.Exists(appFolderPath))
                Directory.CreateDirectory(appFolderPath);
            string netMeasureFilePath = Path.Combine(appFolderPath, network.NetworkName + ".NetMeasure");
            Graph g = new Graph(network.Records, network.Links);
            Dictionary<Guid, double> betweenCentralities =
                VerticeMeasureWorker.CalculateVerticeBetweennessCentrality(g);
            Dictionary<Guid, double> closeCentralities =
                VerticeMeasureWorker.CalculateVerticeCloseCentrality(g);
            Dictionary<Guid, int> degrees =
                VerticeMeasureWorker.GetVerticeDegrees(g.Vertices, g.Edges);
            Dictionary<Guid, int> inDegrees =
                VerticeMeasureWorker.GetVerticeInDegrees(g.Vertices, g.Edges);
            Dictionary<Guid, int> outDegrees =
                VerticeMeasureWorker.GetVerticeOutDegrees(g.Vertices, g.Edges);
            Dictionary<Guid, int> flows =
                VerticeMeasureWorker.GetVerticeFlows(g.Vertices, g.Edges);
            List<EntityBase> prestigiousEntities=new List<EntityBase>();
            List<EntityBase> popularEntities=new List<EntityBase>();
            List<EntityBase> impactEntities=new List<EntityBase>();
            int maxDegree = 0;
            double maxBcScore = 0;
            int maxImScore = 0;
            foreach (Guid vID in g.Vertices.Keys)
            {
                VerticeMeasure vMeasure = new VerticeMeasure();
                vMeasure.ID = g.Vertices[vID].Tag.PKValue();
                vMeasure.Name = g.Vertices[vID].Tag.Name;
                vMeasure.EntityType = g.Vertices[vID].Tag.EntityType;
                vMeasure.Degree = degrees.ContainsKey(vID) ? degrees[vID] : 0;
                vMeasure.InDegree = inDegrees.ContainsKey(vID) ? inDegrees[vID] : 0;
                vMeasure.OutDegree = outDegrees.ContainsKey(vID) ? outDegrees[vID] : 0;
                vMeasure.BetweenCentrality = betweenCentralities.ContainsKey(vID) ? betweenCentralities[vID] : 0;
                vMeasure.CloseCentrality = closeCentralities.ContainsKey(vID) ? closeCentralities[vID] : 0;
                vMeasure.Flow = flows.ContainsKey(vID) ? flows[vID] : 0;
                measures.Add(vMeasure);

                if(vMeasure.BetweenCentrality>maxBcScore)
                {
                    maxBcScore = vMeasure.BetweenCentrality;
                    prestigiousEntities=new List<EntityBase>();
                    prestigiousEntities.Add(g.Vertices[vID].Tag);
                }
                else if(vMeasure.BetweenCentrality==maxBcScore)
                {
                    prestigiousEntities.Add(g.Vertices[vID].Tag);
                }
                if(vMeasure.Degree > maxDegree)
                {
                    maxDegree = vMeasure.Degree;
                    popularEntities=new List<EntityBase>();
                    popularEntities.Add(g.Vertices[vID].Tag);
                }
                else if(vMeasure.Degree==maxDegree)
                {
                    popularEntities.Add(g.Vertices[vID].Tag);
                }
                if(vMeasure.Flow > maxImScore)
                {
                    maxImScore = vMeasure.Flow;
                    impactEntities=new List<EntityBase>();
                    impactEntities.Add(g.Vertices[vID].Tag);
                }
                else if(vMeasure.Flow==maxImScore)
                {
                    impactEntities.Add(g.Vertices[vID].Tag);
                }
            }

            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml("<NetworkMeasure></NetworkMeasure>");
            XmlNode root = xDoc.DocumentElement;
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "VerticeCount", network.Records.Count.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, root, "LinkCount", network.Links.Count.ToString());
            XmlNode vNodes = XmlDataUtil.AddElement(ref xDoc, root, "Vertices");
            foreach (VerticeMeasure vM in measures)
            {
                XmlNode vNode = XmlDataUtil.AddElement(ref xDoc, vNodes, "Vertice");
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "ID", vM.ID);
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "Name", vM.Name);
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "EntityType", vM.EntityType.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "Degree", vM.Degree.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "InDegree", vM.InDegree.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "OutDegree", vM.OutDegree.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "BetweenCentrality", vM.BetweenCentrality.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "CloseCentrality", vM.CloseCentrality.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, vNode, "Flow", vM.Flow.ToString());
            }

            XmlNode prestNodes = XmlDataUtil.AddElement(ref xDoc, root, "PrestigiousEntities");
            foreach(EntityBase entity in prestigiousEntities)
            {
                XmlNode prestNode = XmlDataUtil.AddElement(ref xDoc, prestNodes, "PrestigiousEntity");
                XmlDataUtil.UpdateAttribute(ref xDoc, prestNode, "EntityType", entity.EntityType.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, prestNode, "Key",entity.PKValue());
                XmlDataUtil.UpdateAttribute(ref xDoc, prestNode, "Name", entity.Name);
            }
            XmlNode popularNodes = XmlDataUtil.AddElement(ref xDoc, root, "PopularEntities");
            foreach(EntityBase entity in popularEntities)
            {
                XmlNode popularNode = XmlDataUtil.AddElement(ref xDoc, popularNodes, "PopularEntity");
                XmlDataUtil.UpdateAttribute(ref xDoc, popularNode, "EntityType", entity.EntityType.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, popularNode, "Key", entity.PKValue());
                XmlDataUtil.UpdateAttribute(ref xDoc, popularNode, "Name", entity.Name);
            }
            XmlNode impactNodes = XmlDataUtil.AddElement(ref xDoc, root, "ImpactEntities");
            foreach(EntityBase entity in impactEntities)
            {
                XmlNode impactNode = XmlDataUtil.AddElement(ref xDoc, impactNodes, "ImpactEntity");
                XmlDataUtil.UpdateAttribute(ref xDoc, impactNode, "EntityType", entity.EntityType.ToString());
                XmlDataUtil.UpdateAttribute(ref xDoc, impactNode, "Key", entity.PKValue());
                XmlDataUtil.UpdateAttribute(ref xDoc, impactNode, "Name", entity.Name);
            }

            xDoc.Save(netMeasureFilePath);
        }

        public static Dictionary<EntityType, Dictionary<string, VerticeMeasure>> ToDictionary(List<VerticeMeasure> verticeMeasures)
        {
            Dictionary<EntityType, Dictionary<string, VerticeMeasure>> measureses=new Dictionary<EntityType, Dictionary<string, VerticeMeasure>>();
            foreach(VerticeMeasure vM in verticeMeasures)
            {
                if(measureses.ContainsKey(vM.EntityType))
                {
                    Dictionary<string, VerticeMeasure> eMs = measureses[vM.EntityType];
                    if (!eMs.ContainsKey(vM.ID))
                        eMs.Add(vM.ID, vM);
                    measureses[vM.EntityType]=eMs;
                }
                else
                {
                    Dictionary<string,VerticeMeasure> eMs=new Dictionary<string, VerticeMeasure>();
                    eMs.Add(vM.ID, vM);
                    measureses.Add(vM.EntityType, eMs);
                }
            }
            return measureses;
        }

        public static VerticeMeasureRange GetMeasureRanges(List<VerticeMeasure> measures)
        {
            VerticeMeasureRange range=new VerticeMeasureRange();
            double bcMin = double.PositiveInfinity;
            double bcMax = double.NegativeInfinity;
            double ccMin = double.PositiveInfinity;
            double ccMax = double.NegativeInfinity;
            int dMin = int.MaxValue;
            int dMax = int.MinValue;
            int idMin = int.MaxValue;
            int idMax = int.MinValue;
            int odMin = int.MaxValue;
            int odMax = int.MinValue;
            int flowMin = int.MaxValue;
            int flowMax = int.MinValue;
            foreach (VerticeMeasure measure in measures)
            {
                if (measure.BetweenCentrality > bcMax)
                    bcMax = measure.BetweenCentrality;
                if (measure.BetweenCentrality < bcMin)
                    bcMin = measure.BetweenCentrality;
                if (measure.CloseCentrality > ccMax)
                    ccMax = measure.CloseCentrality;
                if (measure.CloseCentrality < ccMin)
                    ccMin = measure.CloseCentrality;
                if (measure.Degree > dMax)
                    dMax = measure.Degree;
                if (measure.Degree < dMin)
                    dMin = measure.Degree;
                if (measure.InDegree > idMax)
                    idMax = measure.InDegree;
                if (measure.InDegree < idMin)
                    idMin = measure.InDegree;
                if (measure.OutDegree > odMax)
                    odMax = measure.OutDegree;
                if (measure.OutDegree < odMin)
                    odMin = measure.OutDegree;
                if(measure.Flow > flowMax)
                    flowMax = measure.Flow;
                if(measure.Flow < flowMin)
                    flowMin = measure.Flow;
            }
            
            range.BetweenCentralityRange=new Range<double>(bcMin,bcMax);
            range.CloseCentralityRange=new Range<double>(ccMin,ccMax);
            range.DegreeRange=new Range<int>(dMin,dMax);
            range.InDegreeRange=new Range<int>(idMin,idMax);
            range.OutDegreeRange=new Range<int>(odMin,odMax);
            range.FlowRange = new Range<int>(flowMin, flowMax);
            return range;
        }

        public static List<EntityBase> GetPrestigiousEntities(string networkName)
        {
            Network network = Network.Read(networkName);
            Dictionary<EntityType, Dictionary<string, EntityBase>> entities = network.GetEntities();
            List<EntityBase> prestEntities=new List<EntityBase>();
            string appFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolderPath = Path.Combine(appFolderPath, "Network Data");
            if (!Directory.Exists(appFolderPath))
                Directory.CreateDirectory(appFolderPath);
            string netMeasureFilePath = Path.Combine(appFolderPath, networkName + ".NetMeasure");
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(netMeasureFilePath);
            XmlNodeList prestNodes = xDoc.DocumentElement.SelectNodes("PrestigiousEntities/PrestigiousEntity");
            if(prestNodes !=null && prestNodes.Count>0)
            {
                foreach(XmlNode prestNode in prestNodes)
                {
                    EntityType entityType = (EntityType) Enum.Parse(typeof (EntityType),
                                                                    XmlDataUtil.GetAttributeValue(prestNode,
                                                                                                  "EntityType",
                                                                                                  EntityType.Grants.
                                                                                                      ToString()));
                    string key = XmlDataUtil.GetAttributeValue(prestNode, "Key", "");
                    if(entities.ContainsKey(entityType) && entities[entityType].ContainsKey(key))
                    {
                        prestEntities.Add(entities[entityType][key]);
                    }
                }
            }
            return prestEntities;
        }

        public static List<EntityBase> GetPopularEntities(string networkName)
        {
            Network network = Network.Read(networkName);
            Dictionary<EntityType, Dictionary<string, EntityBase>> entities = network.GetEntities();
            List<EntityBase> popularEntities = new List<EntityBase>();
            string appFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolderPath = Path.Combine(appFolderPath, "Network Data");
            if (!Directory.Exists(appFolderPath))
                Directory.CreateDirectory(appFolderPath);
            string netMeasureFilePath = Path.Combine(appFolderPath, networkName + ".NetMeasure");
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(netMeasureFilePath);
            XmlNodeList popularNodes = xDoc.DocumentElement.SelectNodes("PopularEntities/PopularEntity");
            if (popularNodes != null && popularNodes.Count > 0)
            {
                foreach (XmlNode popularNode in popularNodes)
                {
                    EntityType entityType = (EntityType)Enum.Parse(typeof(EntityType),
                                                                    XmlDataUtil.GetAttributeValue(popularNode,
                                                                                                  "EntityType",
                                                                                                  EntityType.Grants.
                                                                                                      ToString()));
                    string key = XmlDataUtil.GetAttributeValue(popularNode, "Key", "");
                    if (entities.ContainsKey(entityType) && entities[entityType].ContainsKey(key))
                    {
                        popularEntities.Add(entities[entityType][key]);
                    }
                }
            }
            return popularEntities;
        }

        public static List<EntityBase> GetImpactEntities(string networkName)
        {
            Network network = Network.Read(networkName);
            Dictionary<EntityType, Dictionary<string, EntityBase>> entities = network.GetEntities();
            List<EntityBase> impactEntities = new List<EntityBase>();
            string appFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            appFolderPath = Path.Combine(appFolderPath, "Network Data");
            if (!Directory.Exists(appFolderPath))
                Directory.CreateDirectory(appFolderPath);
            string netMeasureFilePath = Path.Combine(appFolderPath, networkName + ".NetMeasure");
            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(netMeasureFilePath);
            XmlNodeList impactNodes = xDoc.DocumentElement.SelectNodes("ImpactEntities/ImpactEntity");
            if (impactNodes != null && impactNodes.Count > 0)
            {
                foreach (XmlNode impactNode in impactNodes)
                {
                    EntityType entityType = (EntityType)Enum.Parse(typeof(EntityType),
                                                                    XmlDataUtil.GetAttributeValue(impactNode,
                                                                                                  "EntityType",
                                                                                                  EntityType.Grants.
                                                                                                      ToString()));
                    string key = XmlDataUtil.GetAttributeValue(impactNode, "Key", "");
                    if (entities.ContainsKey(entityType) && entities[entityType].ContainsKey(key))
                    {
                        impactEntities.Add(entities[entityType][key]);
                    }
                }
            }
            return impactEntities;
        }
    }
}
